package calao;

import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
import javax.servlet.http.HttpSession;

import java.io.PrintWriter;
import java.util.HashSet;
import java.util.Locale;

import calao.client.CoreEvents;
import calao.client.Permission;
import calao.util.Printable;
import static calao.util.Logger.*;

/** A workspace session.
 * Sessions shall be used to keep references to workspaces for long periods of time.
 * Direct workspace references shall not be kept for more than a few seconds. Failing
 * to this rule would potentially alter the time it takes to gracefully shutdown the 
 * server.  
 */
public class Session 
implements Printable
{

	class Binder implements HttpSessionBindingListener {
		HttpSession httpSession;
		Binder(HttpSession session) {
			this.httpSession = session;
			session.setAttribute(getSessionKey(ws.path), this);
		}
		public void valueBound(HttpSessionBindingEvent event) { 
			log(DEBUG, "Session %d bound to %s", id, event.getSession().getId());
			assert(event.getSession()==httpSession);
		}
		public void valueUnbound(HttpSessionBindingEvent event) {
			log(DEBUG, "Session %d unbound from %s", id, event.getSession().getId());
			release();
		}
		void release() {
			try {
				httpSession.setAttribute(getSessionKey(ws.path), null);
			}
			catch (NullPointerException e) { }
			httpSession = null;
		}
		Session getSession() {
			return Session.this;
		}
	}

	static int sessionId = 0;

	final int id;
	final int wsid;
	final String alias;
	final Membership membership;
		
	final HashSet<String> permNames = new HashSet<String>();
	
	private Workspace ws;
	private Properties properties;
	private Binder binder;
	
	final long createTime;
	
	Session(Workspace ws, String alias, Membership membership)
	{
		this.ws = ws;
		this.wsid = ws.id;
		this.id = sessionId++;
		this.membership = membership;
		this.alias = alias;
		this.properties = new Properties(ws.properties, membership.properties) {
			@Override
			public void save() {
				Session.this.membership.save();
			}
		};
		for (Profile profile : membership.getProfiles()) {
			for (String permName : profile.getPermissions()) {
				permNames.add(permName);
			}
		}
		createTime = System.currentTimeMillis();
		logEvent(CoreEvents.USER_CONNECTED);
		log(INFO, "New session %d for %s on %s", id, alias, ws.path);
	}
			
	/** Checks whether a permission is available as part of this session.
	 */
	public boolean has(Permission permission)
	{
		return permNames.contains(permission.getName());
	}
	
	/** Gets the workspace.
	 */
	public Workspace getWorkspace()
	{
		return ws;
	}

	public Locale getLocale()
	{
		String lang = getProperties().getProperty(Properties.LANGUAGE);
		if (lang!=null) return new Locale(lang);
		else return Locale.getDefault(); 
	}
	
	public synchronized void close()
	{
		if (binder!=null) binder.release();
		if (ws!=null) {
			logEvent(CoreEvents.USER_DISCONNECTED);
			ws = null;
			properties = null;
			log(INFO, "Closed session %d", id);
		}
	}
		
	public boolean isClosed()
	{
		return ws==null;
	}
	
	@Override
	protected void finalize()
	{
		if (!isClosed()) close();
	}
	
	synchronized void bindTo(HttpSession session)
	{
		if (binder!=null) {
			throw new UnsupportedOperationException("Multiple bind not supported");
		}
		if (isClosed()) {
			throw new RuntimeException("Session already closed");
		}
		binder = new Binder(session);
	}

	private static String getSessionKey(String wsPath) 
	{
		return "calao.session:"+wsPath;
	}
	
	static Session getSession(HttpSession httpSession, String wsPath)
	{
		Binder binder = (Binder)httpSession.getAttribute(getSessionKey(wsPath));
		return binder!=null? binder.getSession() : null;
	}
	
	public User getUser()
	{
		Workspace uws = membership.server.getWorkspace(User.getUWSID(membership.guid));
		User user = uws.getUserManager().getUser(membership.guid);
		if (user==null) log(ERROR, "Cannot retrieve session user %d", membership.guid);
		return user;
	}

	public long getGlobalUserId()
	{
		return membership.guid;
	}
	
	public Membership getMembership()
	{
		return membership;
	}
	
	public Properties getProperties()
	{
		return properties;
	}
	
	void logEvent(String eventName, Object... argv)
	{
		ws.logUserEvent(membership.guid, eventName, argv);
	}

	public void printTo(PrintWriter writer) 
	{
		writer.format("{id=%d, guid=%d, wsid=%d}", id, membership.guid, wsid);
	}
		
}
